/*
 *  This file is part of the Jikes RVM project (http://jikesrvm.org).
 *
 *  This file is licensed to You under the Eclipse Public License (EPL);
 *  You may not use this file except in compliance with the License. You
 *  may obtain a copy of the License at
 *
 *      http://www.opensource.org/licenses/eclipse-1.0.php
 *
 *  See the COPYRIGHT.txt file distributed with this work for information
 *  regarding copyright ownership.
 */
package org.mmtk.plan.colormark;

import org.mmtk.plan.Trace;
import org.mmtk.plan.TraceLocal;
import org.mmtk.policy.Space;
import org.mmtk.utility.Log;
import org.mmtk.utility.options.Options;
import org.mmtk.vm.VM;
import org.vmmagic.pragma.Inline;
import org.vmmagic.pragma.Uninterruptible;
import org.vmmagic.unboxed.ObjectReference;

/**
 * This class implements the thread-local functionality for a transitive
 * closure over a mark-sweep space.
 */
@Uninterruptible
public class CMTraceLocal extends TraceLocal {
  /****************************************************************************
   * Instance fields
   */

  /**
   * Constructor
   */
  public CMTraceLocal(Trace trace) {
    super(CM.SCAN_MARK, trace);
  }
  
  public CMTraceLocal(int scan, Trace trace) {
	super(scan, trace);
  }


  /****************************************************************************
   * Externally visible Object processing and tracing
   */

  /**
   * Is the specified object live?
   *
   * @param object The object.
   * @return <code>true</code> if the object is live.
   */
  @Override
  public boolean isLive(ObjectReference object) {
    if (object.isNull()) return false;
    if (Space.isInSpace(CM.CM_SPACE, object)) {
      return CM.cmSpace.isLive(object);
    }
    return super.isLive(object);
  }

  /**
   * This method is the core method during the trace of the object graph.
   * The role of this method is to:
   *
   * 1. Ensure the traced object is not collected.
   * 2. If this is the first visit to the object enqueue it to be scanned.
   * 3. Return the forwarded reference to the object.
   *
   * In this instance, we refer objects in the mark-sweep space to the
   * msSpace for tracing, and defer to the superclass for all others.
   *
   * @param object The object to be traced.
   * @return The new reference to the same object instance.
   */
  @Inline
  @Override
  public ObjectReference traceObject(ObjectReference object) {
    if (object.isNull()) return object;
    
    if (Space.isInSpace(CM.CM_SPACE, object)) {
    	if(CM.cmSpace.isLive(object)) {
			//traced by regular tracer - do nothing
			return object;
		}
    	ObjectReference color = VM.objectModel.getKathy(object);
    	if(color == ObjectReference.nullReference()) {
    		//object is not traced by helpers, do regular trace
    		return CM.cmSpace.traceObject(this, object);
    	} else {
    		//TODO figure out who traced the object, do not follow up with regular trace
    		CMHelper helper = CM.getHelper(color);
    		if(helper == null) {
    			CM.acquireLogLock();
    			Log.prependThreadId();
    			Log.write("!!! HELPER is NULL for color=");
    			Log.write(color);
    			Log.write(", object=");
    			Log.writeln(object);
    			Log.flush();
    			CM.releaseLogLock();
    		} else {			
    			if(CM.cmSpace.isLive(object)) {
    				CM.acquireLogLock();
    				Log.prependThreadId();
    				Log.write("!!! Already marked and colored color=");
        			Log.write(color);
        			Log.write(", object=");
        			Log.writeln(object);
        			Log.flush();
        			CM.releaseLogLock();
    			} else {
    				if (Options.verbose.getValue() >= 3) {
    					CM.acquireLogLock();
	    				Log.prependThreadId();
	    				Log.write("\tMarking live color=");
	        			Log.write(color);
	        			Log.write(", object=");
	        			Log.writeln(object);
	        			Log.flush();
	        			CM.releaseLogLock();
    				}
    				helper.markLive();
    			}
    			//TODO figure out if helper is working, if no, grab its stack
    			//Log.write("object traced by helper: ");
    			//VM.objectModel.dumpObject(object);
    		}
    		return object;
    	}
    }
    return super.traceObject(object);
  }
  
  protected void giveUpRoots() {
	  this.rootLocations.flushLocal();	  
  }
  
	public void helperTrace() {
		completeTrace();
	}	
	
	/**
	 * Finishing processing all GC work. This method iterates until all work
	 * queues are empty.
	 */
	@Inline
	public void completeTrace() {
		long valuesPopped = 0;
		logMessage(4, "Processing GC in parallel");
		if (!rootLocations.isEmpty()) {
			if (Options.verbose.getValue() >= 1) {
				CM.acquireLogLock();
				Log.prependThreadId();
				Log.writeln("\tcompleteTrace - need to process roots");
				CM.releaseLogLock();
			}
			processRoots();
		}
		logMessage(5, "processing gray objects");
		assertMutatorRemsetsFlushed();
		do {
			while (!values.isEmpty()) {
				ObjectReference v = values.pop();
				if (Options.verbose.getValue() >= 3) {
					CM.acquireLogLock();
					Log.prependThreadId();
					Log.write("\tcompleteTrace - popped v=");Log.writeln(v);
					Log.flush();
					CM.releaseLogLock();
				}
				valuesPopped++;
				scanObject(v);
			}
			processRememberedSets();
		} while (!values.isEmpty());
		if (Options.verbose.getValue() >= 1) {
			CM.acquireLogLock();
			Log.prependThreadId();
			Log.write("\tcompleteTrace - valuesPopped=");Log.writeln(valuesPopped);
			CM.releaseLogLock();
		}
		assertMutatorRemsetsFlushed();
	}
	
	  /**
	 * Process any roots for which processing was delayed.
	 */
	@Inline
	public void processRoots() {
		long poppedRoots = 0;
		while (!rootLocations.isEmpty()) {
			processRootEdge(rootLocations.pop(), true);
			poppedRoots++;
		}
		if (Options.verbose.getValue() >= 1) {
			CM.acquireLogLock();
			Log.prependThreadId();
			Log.write("\tprocessing root objects, popped=");
			Log.writeln(poppedRoots);
			CM.releaseLogLock();
		}
	}
	
	/**
	 * Add a gray object
	 * 
	 * @param object
	 *            The object to be enqueued
	 */
	@Inline
	public void processNode(ObjectReference object) {
		if (Options.verbose.getValue() >= 3) {
			CM.acquireLogLock();
			Log.prependThreadId();
			Log.write("\tprocessNode - pushed object=");
			Log.writeln(object);
			Log.flush();
			CM.releaseLogLock();
		}
		values.push(object);
	}

}
